package client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.concurrent.Semaphore;

import decode.DecodeMessage;

import utils.Utils;
import exception.TrackerException;
import file.BufferMap;
import file.File;
import file.SharedFiles;


public class ConnectedPeer implements Runnable{
	private InputStream inPeer;
	private OutputStream outPeer;
	private Socket sockToPeer;
	private BufferMap peerBufferMap;
	private File _file;
	private int _maxMessageSize;
	private Thread _thread;
	private Semaphore _semaphore;

	public ConnectedPeer(String ipPeer, int portPeer, File f, int max, Semaphore sem){
		this._semaphore = sem;
		try {
			// connection 
			this.sockToPeer = new Socket(ipPeer, portPeer);
			this.inPeer =  this.sockToPeer.getInputStream();
			this.outPeer = this.sockToPeer.getOutputStream();
			_file=f;
			_maxMessageSize = max;
			_thread = new Thread(this, "Connection to a peer");
			_thread.start();
			// interested

		} catch (UnknownHostException e) {
			_semaphore.release();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public ConnectedPeer(Socket s) throws IOException{
		this.sockToPeer = s;
		this.inPeer =  this.sockToPeer.getInputStream();
		this.outPeer = this.sockToPeer.getOutputStream();
	}


	public void isInterestedIn(String keys) throws TrackerException{
		byte[] b = new byte[1024];
		String message = "";
		message += "interested "+ keys;
		try {
			this.outPeer.write(message.getBytes());
			//TODO Fonction de recherche dans les leechs et les seed par rapport à une clé
			String ok = DecodeMessage.decodeMessageFromSocket(this.inPeer, "client", _file);
			if(!ok.matches("^have .*")){
				throw new TrackerException("Bad or No response from Tracker : " + ok);
			}
			else{
				System.out.println(ok);
				//TODO HERE !!!
				ok = ok.split(" ")[2];
				this.peerBufferMap = new BufferMap(_file.getBufferMap().getLength());
				this.peerBufferMap.fillBufferMap(ok.getBytes());
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}



	}

	public boolean getPeerPieces() throws TrackerException{
		//TODO communication entre thread pour pas download 2 fois la même pièce
		int i = 0;
		int bit = 0;
		
		String toSend = "getpieces "+ _file.getIdFile()+ " [";
		BitSet wanted = (BitSet) this.peerBufferMap.getBufferMap().clone();
		int piecesTmp[];
		synchronized (_file){
			wanted.xor(_file.getBufferMap().getBufferMap());

			wanted.and(this.peerBufferMap.getBufferMap());
			if(wanted.isEmpty()){
				return false;
			}
			
			piecesTmp = new int[(int) Math.min((_maxMessageSize/_file.getPieceSize()), wanted.cardinality())];
			
			while(i < (_maxMessageSize/_file.getPieceSize()) && i < wanted.cardinality()){
				
				bit = wanted.nextSetBit(bit);
				
				piecesTmp[i] = bit;
				toSend += bit +" ";
				_file.getBufferMap().addOnePieceToBufferMap(bit);
				i++;
			}
		}
		toSend = toSend.substring(0, toSend.length() - 1);
		toSend += "]";
		try {
			this.outPeer.write(toSend.getBytes());
			String data = DecodeMessage.decodeMessageFromSocket(inPeer, "client", _file);
			if(data != null){
				String regex = "^data "+_file.getIdFile()+" \\[([0-9]+:.*)+\\]";
				if(data.matches(regex)){
					data = data.replaceAll("\\]", "");
					String[] extractData = data.split("\\[")[1].split(" ");
					for(String s : extractData){
						String[] piece = s.split(":");
						if(piece[1].getBytes("ISO-8859-1").length == _file.getPieceSize()){
							
							SharedFiles.setPiece(_file, Integer.parseInt(piece[0]), piece[1].getBytes("ISO-8859-1"));
						}
						else if((Integer.parseInt(piece[0]) == _file.getBufferMap().getLength() - 1 ) &&  piece[1].getBytes("ISO-8859-1").length == (_file.getSize() % _file.getPieceSize()) ){
							SharedFiles.setPiece(_file, Integer.parseInt(piece[0]), piece[1].getBytes("ISO-8859-1"));
						}
						else{
							synchronized (_file) {
								_file.getBufferMap().removeOnePieceFromBufferMap(Integer.parseInt(piece[0]));
							}
							System.out.println("Fichier "+ _file.getIdFile() + " Piece " + piece[0] + " corrompue");
						}
					}
				}
				else{
					synchronized(_file){
						for(int j = 0; j < piecesTmp.length; j++){
							_file.getBufferMap().removeOnePieceFromBufferMap(piecesTmp[j]);
						}
					}
					return false;
				}
			}
			else{
				synchronized(_file){
					for(int j = 0; j < piecesTmp.length; j++){
						_file.getBufferMap().removeOnePieceFromBufferMap(piecesTmp[j]);
					}
				}
				return false;
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;

	}

	/*	public ArrayList<byte[]> parserCetteMerde(String s){

		s = s.replace("[", "");
		s = s.replace("]", "");
		System.out.println(s);//debug
		String [] parsed = s.split(" ");

		String key = parsed[1];
		for(int i = 2; i<parsed.length; i++){

		}

		ArrayList<byte[]> allPieces = new ArrayList<byte[]>();


		return allPieces;
	}
	 */

	public InputStream getInPeer() {
		return inPeer;
	}


	public void setInPeer(InputStream inPeer) {
		this.inPeer = inPeer;
	}


	public OutputStream getOutPeer() {
		return outPeer;
	}


	public void setOutPeer(OutputStream outPeer) {
		this.outPeer = outPeer;
	}


	public Socket getSockToPeer() {
		return sockToPeer;
	}


	public void setSockToPeer(Socket sockToPeer) {
		this.sockToPeer = sockToPeer;
	}


	public BufferMap getBufferMap() {
		return peerBufferMap;
	}

	@Override
	public void run() {
		try {
			this.isInterestedIn(_file.getIdFile());
			if(!this.getPeerPieces()){
				this.sockToPeer.close();
				_semaphore.release();
			}else{
				this.sockToPeer.close();
				_semaphore.release();
			}
		} catch (TrackerException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
